
#include "IniParser.h"
#include <iostream>
#include <strstream>
#include <fstream>

// 一行的结尾,用来分割行
static const std::string LINE_END = "\n";

// 键值对中的=,用来分割键值对
static const std::string KEY_EQUAL = "=";

// 空格，去除字符串左右空格时使用
static const std::string STRING_BLANK = " ";

// section的开始
static const char SECTION_LEFT_TAG = '[';

// section的结尾
static const char SECTION_RIGHT_TAG = ']';

// 注释标志
static const char COMMENT_TAG = ';';

// 数字转字符串临时buffer长度
static const int BUFFER_LEN = 128;
static const int KEY_VALUE_VECTOR_SIZE = 2;

CIniLine::CIniLine(IniLineType type, std::string str)
    : m_type(type)
    , m_comment(str)
{
}

CIniLine::CIniLine(IniLineType type, std::string sectionName, std::string comment)
    : m_type(type)
    , m_stctionName(sectionName)
    , m_comment(comment)
{
}

CIniLine::CIniLine(IniLineType type, std::string key, std::string value, std::string comment)
    : m_type(type)
    , m_key(key)
    , m_value(value)
    , m_comment(comment)
{
}

CIniLine::~CIniLine()
{
    m_vecKeyValue.clear();
}

std::string CIniLine::ToString()
{
    std::string str;
    if (m_type == IniLineType::COMMENT || m_type == IniLineType::BLANK_LINE)
    {
        // 空行也正常保留,保存时不改变原有结构
        str = m_comment + LINE_END;
    }
    else if (m_type == IniLineType::KEY_VALUE_PAIR)
    {
        str = m_key + KEY_EQUAL + m_value;
        if (!m_comment.empty())
        {
            // 键值对值的末尾会加一个空格，方便区分
            str += STRING_BLANK + COMMENT_TAG + m_comment;
        }
        str += LINE_END;
    }
    else if (m_type == IniLineType::SECTION)
    {
        str = SECTION_LEFT_TAG + m_stctionName + SECTION_RIGHT_TAG;
        if (!m_comment.empty())
        {
            str += COMMENT_TAG + m_comment;
        }
        str += LINE_END;
    }

    return str;
}

bool CIniLine::AddItem(std::shared_ptr<CIniLine>& pKeyValue)
{
    if (nullptr != pKeyValue)
    {
        m_vecKeyValue.push_back(pKeyValue);
        return true;
    }
    return false;
}

std::shared_ptr<CIniLine> CIniLine::GetKeyValuePair(const std::string& key)
{
    for (auto pKeyValue : m_vecKeyValue)
    {
        if (nullptr != pKeyValue && pKeyValue->GetKey() == key)
        {
            return pKeyValue;
        }
    }

    return nullptr;
}

bool CIniLine::RemoveLastBlankLine()
{
    if (m_vecKeyValue.empty())
    {
        return false;
    }

    auto pKeyValue = m_vecKeyValue.back();
    if (nullptr != pKeyValue && pKeyValue->GetType() == IniLineType::BLANK_LINE)
    {
        m_vecKeyValue.pop_back();
        return true;
    }

    return false;
}

INI_ITEM_VECTOR& CIniLine::GetAllItem()
{
    return m_vecKeyValue;
}


CIniParser::CIniParser()
    :m_modified(false)
{
}

CIniParser::~CIniParser()
{
    if (m_modified)
    {
        Save();
    }
    ClearAllData();
}

CIniParser::CIniParser(const std::string& file_name)
	:m_modified(false)
    ,m_filePath(file_name)
{
    std::string strFile;
    LoadIniFile(file_name, strFile);
    ParserFromString(strFile);
}

bool CIniParser::LoadIniFile(const std::string& fileName, std::string& str)
{
    SaveBeforeLoad();
    std::ifstream file(fileName);
    if (!file)
    {
        return false;
    }

    m_filePath = fileName;
    file.seekg(0, std::ios::end);
    const long length = file.tellg();
    if (length < 0)
    {
        return false;
    }

    auto buffer = std::make_unique<char[]>(length + 1);
    if (buffer == nullptr)
    {
        return false;
    }
    
    memset(buffer.get(), 0, length + 1);
    file.seekg(0, std::ios::beg);
    file.read(buffer.get(), length);

    const auto readSize = file.gcount();
    buffer[readSize] = '\0';

    str =buffer.get();

    return true;
}

bool CIniParser::ParserFromString(const std::string& str)
{
    if (str.empty())
    {
        return false;
    }

    std::shared_ptr<CIniLine> pCurSection = nullptr;
    const auto vecStrLine = SplitString(str, LINE_END);
    for (std::string strline : vecStrLine)
    {
        std::string strTemp = strline;
        strTemp = StringTrim(strTemp);
        
        auto lineType = IniLineType::UNKNOWN_TPYE;
        if (strTemp.empty())
        {
            lineType = GetLineType(strTemp.at(0));
        }
 
        switch (lineType)
        {
        case IniLineType::SECTION:
        {
            if (nullptr != pCurSection)
            {
                pCurSection->RemoveLastBlankLine();
            }

            // section的开始 
            std::string name, comment;
            ParseInlineComment(strTemp, name, comment);
            auto length = name.length();
            if (name.at(0) == SECTION_LEFT_TAG && name.at(length - 1) == SECTION_RIGHT_TAG)
            {
                name = name.substr(1, length - 2);
                pCurSection = std::make_shared<CIniLine>(IniLineType::SECTION, name, comment);
                if (nullptr != pCurSection)
                {
                    m_vecItems.push_back(pCurSection);
                }
            }
            else
            {
                std::cout << "error section: " << name << std::endl;
            }
        }
        break;
        case IniLineType::KEY_VALUE_PAIR:
        {
            // 键值对 key-value pair
            const auto vecLine = SplitString(strTemp, KEY_EQUAL);
            if (vecLine.size() == KEY_VALUE_VECTOR_SIZE)
            {
                std::string key = StringTrim(vecLine[0]);
                std::string strValue = vecLine[1];

                std::string val, comment;
                ParseInlineComment(strValue, val, comment);

                auto pKeyVal = std::make_shared<CIniLine>(IniLineType::KEY_VALUE_PAIR, key, val, comment);
                if (nullptr != pCurSection && nullptr != pKeyVal)
                {
                    pCurSection->AddItem(pKeyVal);
                }
            }
            else
            {
                std::cout << "parse key-value-pair error! " << std::endl;
            }
        }
        break;
        case IniLineType::COMMENT:
        {
            // 单独一行注释
            auto item = std::make_shared<CIniLine>(IniLineType::COMMENT, strTemp);
            if (nullptr != pCurSection && nullptr != item)
            {
                pCurSection->AddItem(item);
            }
            else
            {
                m_vecItems.push_back(item);
            }
        }
        break;
        case IniLineType::BLANK_LINE:
        {
            // 空行正常保留
            auto item = std::make_shared<CIniLine>(IniLineType::BLANK_LINE, strTemp);
            if (nullptr != pCurSection && nullptr != item)
            {
                pCurSection->AddItem(item);
            }
            else
            {
                m_vecItems.push_back(item);
            }
        }
        break;
        case IniLineType::UNKNOWN_TPYE:
        default:
            break;
        }
    }

    if (nullptr != pCurSection)
    {
        pCurSection->RemoveLastBlankLine();
    }
    return true;
}

IniLineType CIniParser::GetLineType(char ch)
{
    if (ch == COMMENT_TAG)
    {
        // 注释
        return IniLineType::COMMENT;
    }
    else if (ch == SECTION_LEFT_TAG)
    {
        // section
        return IniLineType::SECTION;
    }
    else
    {
        // 键值对 key-value pair
        return IniLineType::KEY_VALUE_PAIR;
    }
    return IniLineType::UNKNOWN_TPYE;
}

bool CIniParser::ParseInlineComment(const std::string& str, std::string& val, std::string& comment)
{
    // 解析一行的末尾是否有注释
    const auto pos = str.find(COMMENT_TAG);
    if (pos != std::string::npos)
    {
        val = str.substr(0, pos);
        val = StringTrim(val); // 去除两端空格

        comment = str.substr(pos + 1);
    }
    else
    {
        val = StringTrim(str);
    }
    return true;
}

std::string CIniParser::GetValue(const std::string& section, const std::string& key)
{
    auto pKeyValue = GetKeyValuePair(section, key);
    if (nullptr != pKeyValue)
    {
        return pKeyValue->GetValue();
    }
    return std::string();
}

std::shared_ptr<CIniLine> CIniParser::GetSectionByName(const std::string& section)
{
    for (auto pItem : m_vecItems)
    {
        if (nullptr == pItem)
        {
            continue;
        }

        if (pItem->GetType() == IniLineType::SECTION && pItem->GetSectionName() == section)
        {
            return pItem;
        }
    }

    return nullptr;
}

std::shared_ptr<CIniLine> CIniParser::GetKeyValuePair(const std::string& section, const std::string& key)
{
    auto pSection = GetSectionByName(section);
    if (nullptr != pSection)
    {
        return pSection->GetKeyValuePair(key);
    }

    return nullptr;
}


std::shared_ptr<CIniLine> CIniParser::CreateSection(const std::string& section, const std::string& comment)
{
    auto pSection = std::make_shared<CIniLine>(IniLineType::SECTION, section, comment);
    if (nullptr != pSection)
    {
        m_vecItems.push_back(pSection);
        return pSection;
    }
    return nullptr;
}

std::shared_ptr<CIniLine> CIniParser::CreateNewKeyValue(std::shared_ptr<CIniLine>& pSection, const std::string& key, const std::string& val, const std::string& comment)
{
    if (nullptr == pSection)
    {
        return pSection;
    }

    auto pKeyVal = std::make_shared<CIniLine>(IniLineType::KEY_VALUE_PAIR, key, val, comment);
    if (nullptr != pKeyVal)
    {
        pSection->AddItem(pKeyVal);
        return pKeyVal;
    }
   
    return nullptr;
}


void CIniParser::SaveBeforeLoad()
{
    if (m_modified)
    {
        Save();
        ClearAllData();
        m_filePath.resize(0);
        m_modified = false;
    }
}

bool CIniParser::Save()
{
    if (m_filePath.empty())
    {
        return false; // file name invalid
    }

    std::ofstream file(m_filePath.c_str());
    if (!file)
    {
        return false;
    }
        
    for (auto pItem : m_vecItems)
    {
        if (pItem != nullptr)
        {
            file << pItem->ToString();
            if (pItem->GetType() == IniLineType::SECTION)
            {
                auto keyValues = pItem->GetAllItem();
                for (auto pKeyValue : keyValues)
                {
                    if (pKeyValue != nullptr)
                    {
                        file << pKeyValue->ToString();
                    }
                }
            }
        }
    }
    m_modified = false;

    return true;
}

bool CIniParser::SaveAs(const std::string& file_name)
{
	std::string old_file_name = m_filePath;
	m_filePath = file_name;

	if (Save())
	{
		return true;
	}

	m_filePath = old_file_name;
	return false;
}

long CIniParser::GetInteger(const std::string& section, const std::string& key, long def_val)
{
    std::istrstream(GetValue(section, key).c_str()) >> def_val;
    return def_val;
}

float CIniParser::GetFloat(const std::string& section, const std::string& key, float def_val)
{
    std::istrstream(GetValue(section, key).c_str()) >> def_val;
    return def_val;
}

long CIniParser::GetString(const std::string& section, const std::string& key, const std::string& def_val, std::string& dst_str)
{
    auto str = GetValue(section, key);
    dst_str = str.length() ? key : def_val;
    return dst_str.length();
}

const std::string CIniParser::GetString(const std::string& section, const std::string& key, const std::string& def_val)
{
    auto str = GetValue(section, key);
    if (str.size() == 0)
    {
        str = def_val;
    }
    return str;
}

void CIniParser::SetInteger(const std::string& section, const std::string& key, long value)
{
    char buffer[BUFFER_LEN + 1] = { 0 };
    std::ostrstream ostr(buffer, BUFFER_LEN);
    ostr << value;
    buffer[ostr.pcount()] = 0;

    SetString(section, key, buffer);
}

void CIniParser::SetFloat(const std::string& section, const std::string& key, float value)
{
    char buffer[BUFFER_LEN + 1] = { 0 };
    std::ostrstream ostr(buffer, BUFFER_LEN);
    ostr << value;
    buffer[ostr.pcount()] = 0;

    SetString(section, key, buffer);
}

void CIniParser::SetString(const std::string& section, const std::string& key, const std::string& value)
{
    auto pKeyValue = GetKeyValuePair(section, key);
    if (nullptr != pKeyValue)
    {
        // 找到了则更新值
        pKeyValue->SetValue(value);
        MarkModified();
        return;
    }

    // 键值对未找到则先查找section是否存在
    auto pSection = GetSectionByName(section);
    if (nullptr != pSection)
    {
        // section存在则在section末尾插入新的键值对
        CreateNewKeyValue(pSection, key, value);
        MarkModified();
    }
    else
    {
        // section存在则末尾增加新的section和键值对
        auto pSection = CreateSection(section);
        CreateNewKeyValue(pSection, key, value);
        MarkModified();
    }
}

void CIniParser::ClearAllData()
{
    m_vecItems.clear();
}

std::vector<std::string> CIniParser::SplitString(const  std::string& s, const std::string& delim)
{
    std::vector<std::string> elems;
    size_t pos = 0;
    size_t len = s.length();
    size_t delim_len = delim.length();
    if (delim_len == 0)
    {
        return elems;
    }

    while (pos < len)
    {
        int find_pos = s.find(delim, pos);
        if (find_pos < 0)
        {
            elems.push_back(s.substr(pos, len - pos));
            break;
        }
        elems.push_back(s.substr(pos, find_pos - pos));
        pos = find_pos + delim_len;
    }

    return elems;
}


std::string CIniParser::StringTrim(const std::string& str)
{
    std::string strTemp = str;
    if (strTemp.empty())
    {
        return strTemp;
    }

    strTemp.erase(0, strTemp.find_first_not_of(STRING_BLANK));
    strTemp.erase(strTemp.find_last_not_of(STRING_BLANK) + 1);
    return strTemp;
}
